/**
 *  This is free software licensed and distributed under 
 *  the terms and conditions of the 
 * GNU Lesser General Public License (LGPL) Version 3.0 .
 * 
 * http://www.gnu.org/licenses/lgpl.html
 *
 * @author Markus Kosmal
 */
package org.mko.html5.client.carousel;

import java.util.ArrayList;

import org.mko.html5.client.basecomponents.figure.Figure;
import org.mko.html5.client.basecomponents.figure.FigureFactory;

import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.UIObject;

// TODO: Auto-generated Javadoc
/**
 * The Class Carousel.
 * 
 * This is made for displaying HTML items in a 3D CSS Carousel. It can be set
 * horizontal or vertical. You can add any valid HTML Item.
 * 
 * With turnLeft and turnRight or turnUp and turnDown you can rotate the
 * Carousel.
 * 
 * Do not set the style attributes of this 3D wonder. Just wrap it in another
 * Panel and add your styles there or give it simply a css style.
 * 
 * @author kosmal
 */
public class Carousel extends FlowPanel {

    /** The INIT. */
    public static final int INIT = 0;

    /** The LEFT. */
    public static final int LEFT = 1;

    /** The RIGHT. */
    public static final int RIGHT = 2;

    /** The inner flow panel. */
    private final FlowPanel innerFlowPanel;

    /** The width. */
    private int width;

    /** The height. */
    private int height;

    /** The size. */
    private int size;

    /** The current rotation degree. */
    private int currentRotationDegree;

    /** The rotate y. */
    private final String rotateY = "rotateY";

    /** The rotate x. */
    private final String rotateX = "rotateX";

    /** The is horizontal. */
    private boolean isHorizontal = true;

    /** The border offset. */
    private int borderOffset;

    /** The border color. */
    private String borderColor;

    /** The perspective multiplication factor. */
    private final int perspectiveMultiplicationFactor = 6;

    /** The degrees in a circle. */
    private final int degreesInACircle = 360;

    /** The minimum list size. */
    private final int minimumListSize = 3;

    /** The carousel items. */
    private final ArrayList<UIObject> carouselItems;

    /**
     * Instantiates a new carousel.
     * 
     * @param width
     *            the width
     * @param height
     *            the height
     * @param size
     *            the size
     */
    public Carousel(final int width, final int height, final int size) {
        super();
        // set size for first round
        this.size = size;
        // remember dimensions for updating
        this.width = width;
        this.height = height;

        // set style to base container
        this.getElement().setAttribute(
                "style",
                "width:" + width + "px;height:" + height + "px;margin: 0 auto 40px;position:relative; perspective:" + (width * this.perspectiveMultiplicationFactor) + "px;-webkit-perspective:"
                        + (width * this.perspectiveMultiplicationFactor) + "px;-ms-perspective:" + (width * this.perspectiveMultiplicationFactor) + "px;-moz-perspective:"
                        + (width * this.perspectiveMultiplicationFactor) + "px;-o-perspective:" + (width * this.perspectiveMultiplicationFactor) + "px;");
        // create inner container
        this.innerFlowPanel = new FlowPanel();

        // init inner container
        this.changeInnerPanelElementStyle(INIT);

        // init carousels items
        this.carouselItems = new ArrayList<UIObject>();

        this.add(this.innerFlowPanel);

        // handle no sizers
        if (size > 0) {
            this.size = size;
        }
        else {
            this.clear();
            this.add(new HTML("no images"));
        }
    }

    /**
     * Instantiates a new carousel.
     * 
     * @param width
     *            the width
     * @param height
     *            the height
     * @param borderOffset
     *            the border offset
     * @param borderColor
     *            the border color
     * @param isHorizontal
     *            the is horizontal
     * @param items
     *            the items
     */
    public Carousel(final int width, final int height, final int borderOffset, final String borderColor, final boolean isHorizontal, final ArrayList<? extends UIObject> items) {
        this(width, height, items.size());
        // set horizontal
        this.isHorizontal = isHorizontal;

        // if size is enough start creating
        if (this.size > 0) {

            // create arraylist of elements to add
            ArrayList<String> stringITems = this.castArrayListUIObjectToArrayListString(items);

            // do create carousel
            this.createCarousel(width, height, borderOffset, borderColor, stringITems);
        }
    }

    /**
     * Creates the carousel.
     * 
     * The Carousel needs to have at least 3 items at the moment to work
     * properly.
     * 
     * @param width
     *            the width
     * @param height
     *            the height
     * @param borderOffset
     *            the border offset
     * @param borderColor
     *            the border color
     * @param stringItems
     *            the string items
     */
    private void createCarousel(final int width, final int height, final int borderOffset, final String borderColor, final ArrayList<String> stringItems) {
        this.borderOffset = borderOffset;
        this.borderColor = borderColor;
        if (this.size > 0) {
            // init figure factory
            FigureFactory factory = new FigureFactory();

            // calc first translate z factor
            int tz = this.calcTranslateZ(width, stringItems.size());

            // init degrees and counter
            int degreeToSet = 0;
            int counter = 0;
            for (String string : stringItems) {

                // create figure
                Figure figure = factory.createFigure(string);

                // set degrees
                if (counter != 0) {
                    degreeToSet += this.degreesInACircle / stringItems.size();
                }

                // set style with transform
                figure.getElement().setAttribute(
                        "style",

                        "display: block;margin:0;position: absolute;width:" + (width - borderOffset) + "px;height:" + (height - borderOffset) + "px;left:" + borderOffset + "px;top:" + borderOffset
                                + "px;border: 2px solid " + borderColor + ";transform:" + this.createRotationString() + "(" + degreeToSet + "deg) translateZ(" + tz + "px);-webkit-transform: "
                                + this.createRotationString() + "(" + degreeToSet + "deg) translateZ(" + tz + "px);-moz-transform: " + this.createRotationString() + "(" + degreeToSet
                                + "deg) translateZ(" + tz + "px);-ms-transform: " + this.createRotationString() + "(" + degreeToSet + "deg) translateZ(" + tz + "px);-o-transform: "
                                + this.createRotationString() + "(" + degreeToSet + "deg) translateZ(" + tz + "px);");

                this.innerFlowPanel.add(figure);
                counter++;
            }
            // rotate if the size is only one, to show the carousel centralized
            if (stringItems.size() == 1) {
                this.turnLeft();
                this.turnLeft();
            }

            // inform user that errors may occur
            if (stringItems.size() < this.minimumListSize) {
                System.out.println("Less than " + this.minimumListSize + " items could lead to errors in rendering the Carousel");
            }
        }
    }

    // TODO: add logic for adding items at runtime
    // @Override
    // public final void add(final Widget w) {
    // this.carouselItems.add(w);
    // this.innerFlowPanel.clear();
    // this.createCarousel(this.width, this.height, this.borderOffset,
    // this.borderColor,
    // this.castArrayListUIObjectToArrayListString(this.carouselItems));
    // }

    /**
     * Cast array list ui object to array list string.
     * 
     * @param items
     *            the items
     * @return the array list
     */
    private ArrayList<String> castArrayListUIObjectToArrayListString(final ArrayList<? extends UIObject> items) {
        ArrayList<String> result = new ArrayList<String>();
        for (UIObject object : items) {
            result.add(object.toString());
        }
        return result;
    }

    /**
     * Creates the rotation string.
     * 
     * @return the string "rotateY" for horizontal "rotateX" for vertical
     */
    private String createRotationString() {
        if (this.isHorizontal) {
            return this.rotateY;
        }
        else {
            return this.rotateX;
        }
    }

    /**
     * Calc translate z.
     * 
     * This method calculates the necessary value of translateZ for a single
     * item in a list of items. It ensures that items in the carousel will be
     * displayed in a circle.
     * 
     * @param width
     *            the width
     * @param size
     *            the size
     * @return the int
     */
    private int calcTranslateZ(final int width, final int size) {
        return (int) Math.round((width / 2) / Math.tan(Math.PI / size));
    }

    /**
     * Change inner panel element style.
     * 
     * This method is used to turn the carousel in any direction. You can use
     * the {@link INIT}, {@link LEFT} and {@link RIGHT} in this class for
     * rotating.
     * 
     * This function is mapped by {@link turnLeft}, {@link turnRight},
     * {@link turnUp} and {@link turnDown} in this class as public.
     * 
     * @param direction
     *            the direction
     */
    private void changeInnerPanelElementStyle(final int direction) {
        if (this.size > 0) {
            // change the current degree according to the direction
            switch (direction) {
                case 1:
                    this.currentRotationDegree += this.degreesInACircle / this.size * -1;
                    break;
                case 2:
                    this.currentRotationDegree -= this.degreesInACircle / this.size * -1;
                    break;
                default:
                    this.currentRotationDegree = this.degreesInACircle / this.size;
                    break;
            }

            // change the style of the inner container panel to show the
            // rotation or initialize it
            this.innerFlowPanel.getElement().setAttribute(
                    "style",
                    "width:100%;height:100%;position:absolute;transform-style: preserve-3d;-webkit-transform-style: preserve-3d;-webkit-transition: -webkit-transform 1s;"
                            + "-moz-transform-style: preserve-3d;-moz-transition: -moz-transform 1s;" + "-o-transform-style: preserve-3d;-o-transition: -o-transform 1s;"
                            + "-ms-transform-style: preserve-3d;-ms-transition: -ms-transform 1s;" + "transition: transform 1s;" + "transform:"
                            + this.createRotationString()
                            + "("
                            + this.currentRotationDegree
                            + "deg) translateZ(-"
                            + 0
                            + "px);"
                            + "-webkit-transform:"
                            + this.createRotationString()
                            + "("
                            + this.currentRotationDegree
                            + "deg) translateZ(-"
                            + 0
                            + "px);"
                            + "-moz-transform:"
                            + this.createRotationString()
                            + "("
                            + this.currentRotationDegree
                            + "deg) translateZ(-0px);-ms-transform:"
                            + this.createRotationString()
                            + "("
                            + this.currentRotationDegree
                            + "deg) translateZ(-"
                            + 0
                            + "px);"
                            + "-o-transform:"
                            + this.createRotationString() + "(" + this.currentRotationDegree + "deg) translateZ(-" + 0 + "px);");
        }
    }

    /**
     * Turn left.
     */
    public final void turnLeft() {
        this.changeInnerPanelElementStyle(LEFT);
    }

    /**
     * Turn up.
     */
    public final void turnUp() {
        this.changeInnerPanelElementStyle(LEFT);
    }

    /**
     * Turn right.
     */
    public final void turnRight() {
        this.changeInnerPanelElementStyle(RIGHT);
    }

    /**
     * Turn down.
     */
    public final void turnDown() {
        this.changeInnerPanelElementStyle(RIGHT);
    }

    /**
     * Overriding the setStyle Method - please wrap the Carousel instead.
     * 
     * @param styleName
     *            the new style name
     * @see com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
     */
    @Override
    public final void setStyleName(final String styleName) {
        // Do nothing - changing the style is not allowed
        System.out.println("Do nothing - changing the style of Carousel is not allowed, wrap it instead");
    }

    /**
     * Gets the width.
     * 
     * @return the width
     */
    public final int getWidth() {
        return this.width;
    }

    /**
     * Sets the width.
     * 
     * @param width
     *            the width to set
     */
    public final void setWidth(final int width) {
        this.width = width;
    }

    /**
     * Gets the height.
     * 
     * @return the height
     */
    public final int getHeight() {
        return this.height;
    }

    /**
     * Sets the height.
     * 
     * @param height
     *            the height to set
     */
    public final void setHeight(final int height) {
        this.height = height;
    }

    /**
     * Gets the size.
     * 
     * @return the size
     */
    public final int getSize() {
        return this.size;
    }

    /**
     * Sets the size.
     * 
     * @param size
     *            the size to set
     */
    public final void setSize(final int size) {
        this.size = size;
    }

    /**
     * Checks if is horizontal.
     * 
     * @return the isHorizontal
     */
    public final boolean isHorizontal() {
        return this.isHorizontal;
    }

    /**
     * Sets the horizontal.
     * 
     * @param isHorizontal
     *            the isHorizontal to set
     */
    public final void setHorizontal(final boolean isHorizontal) {
        this.isHorizontal = isHorizontal;
    }

    /**
     * Gets the border offset.
     * 
     * @return the borderOffset
     */
    public final int getBorderOffset() {
        return this.borderOffset;
    }

    /**
     * Sets the border offset.
     * 
     * @param borderOffset
     *            the borderOffset to set
     */
    public final void setBorderOffset(final int borderOffset) {
        this.borderOffset = borderOffset;
    }

    /**
     * Gets the border color.
     * 
     * @return the borderColor
     */
    public final String getBorderColor() {
        return this.borderColor;
    }

    /**
     * Sets the border color.
     * 
     * @param borderColor
     *            the borderColor to set
     */
    public final void setBorderColor(final String borderColor) {
        this.borderColor = borderColor;
    }

    /**
     * Gets the carousel items.
     * 
     * @return the carouselItems
     */
    public final ArrayList<UIObject> getCarouselItems() {
        return this.carouselItems;
    }

}
